package analysisAndCode;

import entity.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Target {

    // 中间代码
    private List<SameQuaternion> quaternionList;

    // 符号表
    private SymbolTable symbolTable;

    // 目标代码
    private String assemblyCode;

    // 临时变量个数
    private int tempVariableNum;
    // 局部变量个数
    private int localVariableNum;

    // 函数区域
    private Map<String, String> functionScope;

    // 是否在调用函数里
    private boolean flag;

    // 目标代码生成
    public void target(){
        beforeOwnCode();
        int index = quaternionList.size();
        int i = 0;
        while(i < index){
            SameQuaternion quaternion = quaternionList.get(i);
            switch (quaternion.getOperate()){
                case "main": break;
                case "sys": {
                    assemblyCode += "quit: mov ah,4ch\n";
                    assemblyCode += "int 21h\n";
                }break;
                case "=": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    MovFromRegister(quaternion.getResult(), "AX");
                }break;
                case "+": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    AddRegister("AX", quaternion.getOperate2());
                    MovFromRegister(quaternion.getResult(), "AX");
                }break;
                case "-": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    SubRegister("AX", quaternion.getOperate2());
                    MovFromRegister(quaternion.getResult(), "AX");
                }break;
                case "*": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    MovToRegister("BX", quaternion.getOperate2());
                    MulRegister("BX");
                    MovFromRegister(quaternion.getResult(), "AX");
                }break;
                case "/": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    MovToRegister("DX", "0");
                    MovToRegister("BX", quaternion.getOperate2());
                    DivRegister("BX");
                    MovFromRegister(quaternion.getResult(), "AX");
                }break;
                case "%": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    MovToRegister("DX", "0");
                    MovToRegister("BX", quaternion.getOperate2());
                    DivRegister("BX");
                    MovFromRegister(quaternion.getResult(), "DX");
                }break;
                case "j<": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    CmpRegister("AX", quaternion.getOperate2());
                    JBRegister(quaternion.getResult());
                }break;
                case "j>=": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    CmpRegister("AX", quaternion.getOperate2());
                    JNBRegister(quaternion.getResult());
                }break;
                case "j>": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    CmpRegister("AX", quaternion.getOperate2());
                    JARegister(quaternion.getResult());
                }break;
                case "j<=": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    CmpRegister("AX", quaternion.getOperate2());
                    JNARegister(quaternion.getResult());
                }break;
                case "j==": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    CmpRegister("AX", quaternion.getOperate2());
                    JERegister(quaternion.getResult());
                }break;
                case "j!=": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    CmpRegister("AX", quaternion.getOperate2());
                    JNERegister(quaternion.getResult());
                }break;
                case "&&": {
                    assemblyLabel(i);
                    MovToRegister("DX", "0");
                    MovToRegister("AX", quaternion.getOperate1());
                    CmpRegister("AX", "0");
                    JERegister("AND", i);
                    MovToRegister("AX", quaternion.getOperate2());
                    CmpRegister("AX", "0");
                    JERegister("AND", i);
                    MovToRegister("DX", "1");
                    assemblyLabel("AND_" + i);
                    MovFromRegister(quaternion.getResult(), "DX");
                }break;
                case "||": {
                    assemblyLabel(i);
                    MovToRegister("DX", "1");
                    MovToRegister("AX", quaternion.getOperate1());
                    CmpRegister("AX", "0");
                    JNERegister("OR", i);
                    MovToRegister("AX", quaternion.getOperate2());
                    CmpRegister("AX", "0");
                    JNERegister("OR", i);
                    MovToRegister("DX", "0");
                    assemblyLabel("OR_" + i);
                    MovFromRegister(quaternion.getResult(), "DX");
                }break;
                case "j!": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    CmpRegister("AX", "0");
                    JERegister(quaternion.getResult());
                }break;
                case "j": {
                    assemblyLabel(i);
                    JMPTo(quaternion.getResult());
                }break;
                case "jnz": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    CmpRegister("AX", "1");
                    JERegister(quaternion.getResult());
                }break;
                case "para": {
                    assemblyLabel(i);
                    MovToRegister("AX", quaternion.getOperate1());
                    PushAX();
                }break;
                case "call": {
                    assemblyLabel(i);
                    addCALL();
                    assemblyCode += quaternion.getOperate1();
                    nextLine();
                    if(!quaternion.getResult().equals(" ")){
                        MovFromRegister(quaternion.getResult(), "AX");
                    }
                }break;
                case "ret": {
                    assemblyLabel(i);
                    if(!quaternion.getResult().equals(" ")){
                        MovToRegister("AX", quaternion.getResult());
                    }
                    MovBPToSP();
                    PopBP();
                    RET();
                    flag = false;
                }break;
                default: {
                    assemblyCode += quaternion.getOperate() + ": ";
                    PushBP();
                    MovSPToBP();
                    SubSP(quaternion.getOperate());
                    flag = true;
                    handleIntermediateCode(i + 1);
                }
            }
            assemblyCode += "\n";
            i++;
        }
        afterOwnCode();
    }

    private void handleIntermediateCode(int i) {
        Map<String, String> newName = new HashMap<>();
        int localIndex = 0;
        int tempIndex = localVariableNum * 2;
        int paraIndex = 4;
        while (i < quaternionList.size()){
            SameQuaternion quaternion = quaternionList.get(i);
            String name = null;
            if(quaternion.getOperate().equals("call")){
                name = quaternion.getOperate1();
            }
            String op = quaternion.getOperate1();
            if(!op.equals(" ")){
                if(newName.containsKey(op)){
                    quaternion.setOperate1(newName.get(op));
                }else {
                    if(op.charAt(0) < '0' || op.charAt(0) > '9'){
                        if(op.charAt(0) == 'T'){
                            tempIndex = (localVariableNum + Integer.parseInt(op.substring(1)) - 1) * 2;
                            quaternion.setOperate1("ss:[bp-" + tempIndex + "]");
                            newName.put(op, "ss:[bp-" + tempIndex + "]");
                        }else {
                            if(isLocalVariable(op)){
                                quaternion.setOperate1("ss:[bp-" + localIndex + "]");
                                newName.put(op, "ss:[bp - " + localIndex + "]");
                                localIndex += 2;
                            }else {
                                quaternion.setOperate1("ss:[bp+" + paraIndex + "]");
                                newName.put(op, "ss:[bp+" + paraIndex + "]");
                                paraIndex += 2;
                            }
                        }
                    }
                }
            }
            op = quaternion.getOperate2();
            if(!op.equals(" ")){
                if(newName.containsKey(op)){
                    quaternion.setOperate2(newName.get(op));
                }else {
                    if(op.charAt(0) < '0' || op.charAt(0) > '9'){
                        if(op.charAt(0) == 'T'){
                            tempIndex = (localVariableNum + Integer.parseInt(op.substring(1)) - 1) * 2;
                            quaternion.setOperate2("ss:[bp-" + tempIndex + "]");
                            newName.put(op, "ss:[bp-" + tempIndex + "]");
                        }else {
                            if(isLocalVariable(op)){
                                quaternion.setOperate2("ss:[bp-" + localIndex + "]");
                                newName.put(op, "ss:[bp - " + localIndex + "]");
                                localIndex += 2;
                            }else {
                                quaternion.setOperate2("ss:[bp+" + paraIndex + "]");
                                newName.put(op, "ss:[bp+" + paraIndex + "]");
                            }
                        }
                    }
                }
            }
            op = quaternion.getResult();
            if(!op.equals(" ")){
                if(newName.containsKey(op)){
                    quaternion.setResult(newName.get(op));
                }else {
                    if(op.charAt(0) < '0' || op.charAt(0) > '9'){
                        if(op.charAt(0) == 'T'){
                            tempIndex = (localVariableNum + Integer.parseInt(op.substring(1)) - 1) * 2;
                            quaternion.setResult("ss:[bp-" + tempIndex + "]");
                            newName.put(op, "ss:[bp-" + tempIndex + "]");
                        }else {
                            if(isLocalVariable(op)){
                                quaternion.setResult("ss:[bp-" + localIndex + "]");
                                newName.put(op, "ss:[bp - " + localIndex + "]");
                                localIndex += 2;
                            }else {
                                quaternion.setResult("ss:[bp+" + paraIndex + "]");
                                newName.put(op, "ss:[bp+" + paraIndex + "]");
                            }
                        }
                    }
                }
            }
            if(quaternion.getOperate().equals("call")){
                quaternion.setOperate1(name);
            }
            String text = "";
                text += i + "\t( ";
                text += quaternionList.get(i).getOperate() + ", ";
                text += quaternionList.get(i).getOperate1() + ", ";
                text += quaternionList.get(i).getOperate2() + ", ";
                text += quaternionList.get(i).getResult() + " )";
            System.out.println(text);
            i++;
            if(quaternion.getOperate().equals("ret")){
                break;
            }
        }
    }

    private void beforeOwnCode() {
        assemblyCode += "" +
                "assume cs:code,ds:data,ss:stack,es:extended\n" +
                "\n" +
                "extended segment\n" +
                "db 1024 dup (0)\n" +
                "extended ends\n" +
                "\n" +
                "stack segment\n" +
                "db 1024 dup (0)\n" +
                "stack ends\n" +
                "\n" +
                "dispmsg macro message\n" +
                "lea dx, message\n" +
                "mov ah, 9\n" +
                "int 21h\n" +
                "endm\n" +
                "\n" +
                "data segment\n" +
                "_buff_p db 256 dup (24h)\n" +
                "_buff_s db 256 dup (0)\n" +
                "_msg_p db 0ah,'Output:',0\n" +
                "_msg_s db 0ah,'Input:',0  \n" +
                "next_row db 0dh,0ah,'$'\n" +
                "error db 'input error, please re-enter: ','$'\n";
        // 添加变量
        assemblyCode += variableDefinitions();

        assemblyCode += "" +
                "data ends\n" +
                "\n" +
                "code segment\n" +
                "start: mov ax,extended\n" +
                "mov es,ax\n" +
                "mov ax,stack\n" +
                "mov ss,ax\n" +
                "mov sp,1024\n" +
                "mov bp,sp\n" +
                "mov ax,data\n" +
                "mov ds,ax\n\n";
    }

    private void afterOwnCode(){
        assemblyCode += "\n" +
                "read proc near\n" +
                "push bp\n" +
                "mov bp, sp\n" +
                "mov bx,offset _msg_s\n" +
                "call _print\n" +
                "push bx\n" +
                "push cx\n" +
                "push dx\n" +
                "proc_pre_start:\n" +
                "xor ax, ax\n" +
                "xor bx, bx\n" +
                "xor cx, cx\n" +
                "xor dx, dx\n" +
                "proc_judge_sign:\n" +
                "mov ah, 1\n" +
                "int 21h\n" +
                "cmp al, '-'\n" +
                "jne proc_next\n" +
                "mov dx, 0ffffh\n" +
                "jmp proc_digit_in\n" +
                "proc_next:\n" +
                "cmp al, 30h\n" +
                "jb proc_unexpected\n" +
                "cmp al, 39h\n" +
                "ja proc_unexpected\n" +
                "sub al, 30h\n" +
                "shl bx, 1\n" +
                "mov cx, bx\n" +
                "shl bx, 1\n" +
                "shl bx, 1\n" +
                "add bx, cx\n" +
                "add bl, al\n" +
                "adc bh, 0\n" +
                "proc_digit_in:\n" +
                "mov ah, 1\n" +
                "int 21h\n" +
                "jmp proc_next\n" +
                "\n" +
                "proc_save:\n" +
                "cmp dx, 0ffffh\n" +
                "jne proc_result_save\n" +
                "neg bx\n" +
                "proc_result_save:\n" +
                "mov ax, bx\n" +
                "jmp proc_input_done\n" +
                "\n" +
                "proc_unexpected:\n" +
                "cmp al, 0dh\n" +
                "je proc_save\n" +
                "dispmsg next_row\n" +
                "dispmsg error\n" +
                "jmp proc_pre_start\n" +
                "\n" +
                "proc_input_done:\n" +
                "pop dx\n" +
                "pop cx\n" +
                "pop bx\n" +
                "pop bp\n" +
                "ret\n" +
                "read endp\n" +
                "\n" +
                "write proc near\n" +
                "push bp\n" +
                "mov bp, sp\n" +
                "push ax\n" +
                "push bx\n" +
                "push cx\n" +
                "push dx\n" +
                "mov bx,offset _msg_p\n" +
                "call _print\n" +
                "xor cx, cx\n" +
                "mov bx, [bp+4]\n" +
                "test bx, 8000h\n" +
                "jz proc_nonneg\n" +
                "neg bx\n" +
                "mov dl,'-'\n" +
                "mov ah, 2\n" +
                "int 21h\n" +
                "proc_nonneg:\n" +
                "mov ax, bx\n" +
                "cwd\n" +
                "mov bx, 10\n" +
                "proc_div_again:\n" +
                "xor dx, dx\n" +
                "div bx\n" +
                "add dl, 30h\n" +
                "push dX\n" +
                "inc cx\n" +
                "cmp ax, 0\n" +
                "jne proc_div_again\n" +
                "proc_digit_out:\n" +
                "pop dx\n" +
                "mov ah, 2\n" +
                "int 21h\n" +
                "loop proc_digit_out\n" +
                "proc_output_done:\n" +
                "pop dx\n" +
                "pop cx\n" +
                "pop bx\n" +
                "pop ax\n" +
                "pop bp\n" +
                "ret 2\n" +
                "write endp\n" +
                "\n" +
                "_print: mov si,0\n" +
                "mov di,offset _buff_p\n" +
                "_p_lp_1: mov al,ds:[bx+si]\n" +
                "cmp al,0\n" +
                "je _p_brk_1\n" +
                "mov ds:[di],al\n" +
                "inc si\n" +
                "inc di\n" +
                "jmp short _p_lp_1\n" +
                "_p_brk_1: mov dx,offset _buff_p\n" +
                "mov ah,09h\n" +
                "int 21h\n" +
                "mov cx,si\n" +
                "mov di,offset _buff_p\n" +
                "_p_lp_2: mov al,24h\n" +
                "mov ds:[di],al\n" +
                "inc di\n" +
                "loop _p_lp_2\n" +
                "ret\n" +
                "code ends\n" +
                "end start\n";
    }

    private String variableDefinitions() {
        String res = "";
        for (Variable variable : symbolTable.getVariableList()) {
            String op = variable.getName();
            if(op.charAt(0) >= '0' && op.charAt(0) <= '9'){

            }else if(op.charAt(0) == 'T'){

            }else {
                res += "_" + op + " dw 0\n";
            }
        }
        return res;
    }

    private void SubSP(String functionName){
        assemblyCode += "SUB SP," + getByteSize(functionName);
        nextLine();
    }

    private String getByteSize(String functionName) {
        tempVariableNum = 0;
        localVariableNum = 0;
        String scope = functionScope.get(functionName);
        for (Variable variable : symbolTable.getVariableList()) {
            String op = variable.getName();
            if(!(op.charAt(0) >= '0' && op.charAt(0) <= '9')){
                if(op.charAt(0) == 'T'){
                    tempVariableNum++;
                }else {
                    List<VariableWSN> variableWSN = variable.getVariableWSN();
                    for (int m = 0; m < variableWSN.size(); m++) {
                        // 3.4、查看当前作用域
                        if(variableWSN.get(m).getScope().length() == scope.length()){
                            if(variableWSN.get(m).getScope().equals(scope)){
                                localVariableNum++;
                            }
                        }else if(variableWSN.get(m).getScope().length() > scope.length()){
                            if (variableWSN.get(m).getScope().startsWith(scope)) {
                                localVariableNum++;
                            }
                        }
                    }
                }
            }
        }
        return String.valueOf((localVariableNum + tempVariableNum) * 2);
    }

    private void RET(){
        assemblyCode += "RET";
        nextLine();
    }

    private void PopBP(){
        assemblyCode += "POP BP";
        nextLine();
    }

    private void PushAX(){
        assemblyCode += "PUSH AX";
        nextLine();
    }

    private void PushBP(){
        assemblyCode += "PUSH BP";
        nextLine();
    }

    private void JMPTo(String target){
        assemblyCode += "JMP far ptr _" + target;
        nextLine();
    }

    private void JERegister(String target){
        assemblyCode += "JE _" + target;
        nextLine();
    }

    private void JERegister(String target, int i){
        assemblyCode += "JE _" + target + "_" + i;
        nextLine();
    }

    private void JNERegister(String target){
        assemblyCode += "JNE _" + target;
        nextLine();
    }

    private void JNERegister(String target, int i){
        assemblyCode += "JNE _" + target + "_" + i;
        nextLine();
    }

    private void JNBRegister(String target){
        assemblyCode += "JNB _" + target;
        nextLine();
    }

    private void JBRegister(String target){
        assemblyCode += "JB _" + target;
        nextLine();
    }

    private void JNARegister(String target){
        assemblyCode += "JNA _" + target;
        nextLine();
    }

    private void JARegister(String target){
        assemblyCode += "JA _" + target;
        nextLine();
    }

    private void CmpRegister(String registerName, String source){
        addCMP();
        switchRegister(registerName);
        addComma();
        assemblyCode += getAssign(source);
        nextLine();
    }

    private void DivRegister(String registerName){
        addDIV();
        switchRegister(registerName);
        nextLine();
    }

    private void MulRegister(String registerName){
        addMUL();
        switchRegister(registerName);
        nextLine();
    }

    private void AddRegister(String registerName, String source){
        addADD();
        switchRegister(registerName);
        addComma();
        assemblyCode += getAssign(source);
        nextLine();
    }

    private void SubRegister(String registerName, String source){
        addSUB();
        switchRegister(registerName);
        addComma();
        assemblyCode += getAssign(source);
        nextLine();
    }

    private void MovBPToSP(){
        addMOV();
        assemblyCode += "SP";
        addComma();
        assemblyCode += "BP";
        nextLine();
    }

    private void MovSPToBP(){
        addMOV();
        assemblyCode += "BP";
        addComma();
        assemblyCode += "SP";
        nextLine();
    }

    private void MovToRegister(String registerName, String source){
        addMOV();
        switchRegister(registerName);
        addComma();
        assemblyCode += getAssign(source);
        nextLine();
    }

    private void MovFromRegister(String target, String registerName){
        addMOV();
        assemblyCode += getAssign(target);
        addComma();
        switchRegister(registerName);
        nextLine();
    }

    private void switchRegister(String registerName){
        switch (registerName){
            case "AX": addAX();break;
            case "BX": addBX();break;
            case "CX": addCX();break;
            case "DX": addDX();break;
        }
    }

    private void  addCMP(){
        assemblyCode += "CMP ";
    }

    private void  addMUL(){
        assemblyCode += "MUL ";
    }

    private void  addDIV(){
        assemblyCode += "DIV ";
    }

    private void  addSUB(){
        assemblyCode += "SUB ";
    }

    private void  addADD(){
        assemblyCode += "ADD ";
    }

    private void nextLine(){
        assemblyCode += "\n";
    }

    private void addMOV(){
        assemblyCode += "MOV ";
    }
    private void addCALL(){
        assemblyCode += "CALL ";
    }

    private void addAX(){
        assemblyCode += "AX";
    }

    private void addBX(){
        assemblyCode += "BX";
    }

    private void addCX(){
        assemblyCode += "CX";
    }

    private void addDX(){
        assemblyCode += "DX";
    }

    private void addComma(){
        assemblyCode += ",";
    }

    private String getAssign(String op){
        if (flag == false){
            if(op.charAt(0) >= '0' && op.charAt(0) <= '9'){
                return op;
            }else if(op.charAt(0) == 'T'){
                op = op.substring(1);
                return "es:[" + (Integer.parseInt(op) * 2) + "]";
            }else {
                return "ds:[_" + op + "]";
            }
        }else {
            return op;
        }

    }

    private boolean isLocalVariable(String name) {
        for (Variable variable : symbolTable.getVariableList()) {
            String op = variable.getName();
            if(op.equals(name)){
                List<VariableWSN> variableWSN = variable.getVariableWSN();
                boolean is = true;
                for (int m = 0; m < variableWSN.size(); m++) {
                    // 3.4、查看当前作用域
                    if(variableWSN.get(m).getScope().equals("/0")){
                        is = false;
                    }
                }
                if (is == false){
                    return false;
                }
            }
        }
        return true;
    }

    private void assemblyLabel(String name){
        assemblyCode += "_" + name + ": ";
    }
    private void assemblyLabel(int name){
        assemblyCode += "_" + name + ": ";
    }

    public Target() {
    }

    public Target(List<SameQuaternion> quaternionList, SymbolTable symbolTable, Map<String, String> functionScope) {
        this.quaternionList = quaternionList;
        this.symbolTable = symbolTable;
        this.assemblyCode = "";
        this.functionScope = functionScope;
        this.flag = false;
    }

    public List<SameQuaternion> getQuaternionList() {
        return quaternionList;
    }

    public void setQuaternionList(List<SameQuaternion> quaternionList) {
        this.quaternionList = quaternionList;
    }

    public SymbolTable getSymbolTable() {
        return symbolTable;
    }

    public void setSymbolTable(SymbolTable symbolTable) {
        this.symbolTable = symbolTable;
    }

    public String getAssemblyCode() {
        return assemblyCode;
    }

    public void setAssemblyCode(String assemblyCode) {
        this.assemblyCode = assemblyCode;
    }
}
